use bevy::{
    input::keyboard::KeyboardInput, prelude::*, sprite::MaterialMesh2dBundle,
    utils::hashbrown::HashSet,
};
use rand::{random, thread_rng, Rng};
pub mod component;
pub mod resize;
use component::*;
use resize::*;
fn main() {
    App::new()
        .insert_resource(WindowSize::default())
        .insert_resource(BlockSize::default())
        .add_plugins(DefaultPlugins)
        .add_state::<Direction>()
        .add_state::<FoodState>()
        .add_state::<MoveState>()
        .add_event::<FoodEaten>()
        .add_systems(Startup, setup)
        .add_systems(OnEnter(MoveState::Done), (set_block, eat))
        .add_systems(Update, turn)
        .add_systems(Update, check_collide)
        .add_systems(Update, resize)
        .add_systems(OnEnter(FoodState::NonExist), create_food)
        .add_systems(Update, update)
        .run()
}
const SIZE: (u32, u32) = (10, 10);
#[derive(Resource, Default, Debug, Clone)]
pub struct WindowSize {
    width: f32,
    height: f32,
}
#[derive(Event)]
pub struct FoodEaten {
    food: Color,
}
#[derive(Resource, Default, Debug)]
pub struct BlockSize {
    width: f32,
}

#[derive(States, Hash, Default, Debug, Clone, PartialEq, PartialOrd, Ord, Eq)]
enum Direction {
    #[default]
    Up,
    Down,
    Left,
    Right,
}
#[derive(States, Hash, Default, Debug, Clone, PartialEq, PartialOrd, Ord, Eq)]
enum FoodState {
    Exist,
    #[default]
    NonExist,
}
#[derive(States, Hash, Default, Debug, Clone, PartialEq, PartialOrd, Ord, Eq)]
enum MoveState {
    Ing,
    #[default]
    Done,
}
fn setup(mut commands: Commands, window_size: ResMut<WindowSize>, block_size: ResMut<BlockSize>) {
    commands.spawn(Camera2dBundle::default());
    commands
        .spawn(SpriteBundle {
            sprite: Sprite {
                custom_size: Some(Vec2::new(block_size.width, block_size.width)),
                ..Default::default()
            },
            transform: Transform {
                translation: to_translation(&window_size, &block_size, 0, 0),
                ..Default::default()
            },
            ..Default::default()
        })
        .insert(Block {
            arrive: false,
            ..Default::default()
        })
        .insert(Snake);
}
fn turn(
    mut input: EventReader<KeyboardInput>,
    mut next_direction: ResMut<NextState<Direction>>,
    direction: Res<State<Direction>>,
) {
    input.read().for_each(|e| match e.key_code {
        Some(value) => match value {
            KeyCode::Left if *direction.get() != Direction::Right => {
                next_direction.set(Direction::Left)
            }
            KeyCode::Right if *direction.get() != Direction::Left => {
                next_direction.set(Direction::Right)
            }
            KeyCode::Up if *direction.get() != Direction::Down => next_direction.set(Direction::Up),
            KeyCode::Down if *direction.get() != Direction::Up => {
                next_direction.set(Direction::Down)
            }
            _ => (),
        },
        _ => (),
    });
}
fn update(
    mut query: Query<(&mut Transform, &mut Block), With<Snake>>,
    block_size: Res<BlockSize>,
    move_state: Res<State<MoveState>>,
    mut next_move_state: ResMut<NextState<MoveState>>,
    window_size: Res<WindowSize>,
) {
    if *move_state.get() == MoveState::Done {
        return;
    }
    println!("up");
    let mut query = query.iter_mut().collect::<Vec<_>>();
    const STEP: f32 = 10.;
    query
        .iter_mut()
        .rev()
        .peekable()
        .map(|(ref mut transfrom, block)| {
            if block.arrive {
                transfrom.translation = to_translation(
                    &window_size,
                    &block_size,
                    block.location.0,
                    block.location.1,
                );
            } else {
                transfrom.translation += (to_translation(
                    &window_size,
                    &block_size,
                    block.location.0,
                    block.location.1,
                ) - to_translation(
                    &window_size,
                    &block_size,
                    block.old_location.0,
                    block.old_location.1,
                )) / STEP;
            }
            (transfrom.translation, block)
        })
        .filter(|(_, block)| !block.arrive)
        .map(|(current, block)| {
            if {
                let goal = to_translation(
                    &window_size,
                    &block_size,
                    block.location.0,
                    block.location.1,
                );
                (goal.x - current.x).abs() < block_size.width / STEP
                    && (goal.y - current.y).abs() < block_size.width / STEP
            } {
                block.arrive = true;
            }
            block.arrive
        })
        .all(|arrive| arrive)
        .then(|| {
            next_move_state.set(MoveState::Done);
            println!("Done")
        });
}
fn set_block(
    mut query: Query<&mut Block, With<Snake>>,
    direction: Res<State<Direction>>,
    mut food_eaten_event: EventReader<FoodEaten>,
    block_size: Res<BlockSize>,
    mut commands: Commands,
    mut next_move_state: ResMut<NextState<MoveState>>,
    window_size: Res<WindowSize>,
) {
    println!("set");
    let mut query_block = query.iter_mut().collect::<Vec<_>>();
    let last_block = (**query_block.last().unwrap()).clone();
    let mut query_block_iter = query_block.iter_mut().rev().peekable();
    while let Some(block) = query_block_iter.next() {
        block.arrive = false;
        if let Some(next) = query_block_iter.peek() {
            block.location = next.location;
            block.old_location = next.old_location;
        }
    }
    query_block.first_mut().unwrap().old_location = query_block.first().unwrap().location;
    let location = &mut query_block.first_mut().unwrap().location;
    match direction.get() {
        Direction::Up => {
            if location.1 == SIZE.1 - 1 {
                location.1 = 0;
            } else {
                location.1 += 1;
            }
        }
        Direction::Down => {
            if location.1 == 0 {
                location.1 = SIZE.1 - 1;
            } else {
                location.1 -= 1;
            }
        }
        Direction::Left => {
            if location.0 == 0 {
                location.0 = SIZE.0 - 1;
            } else {
                location.0 -= 1;
            }
        }
        Direction::Right => {
            if location.0 == SIZE.0 - 1 {
                location.0 = 0;
            } else {
                location.0 += 1;
            }
        }
    };

    food_eaten_event.read().for_each(|food| {
        println!("read");
        commands
            .spawn(SpriteBundle {
                sprite: Sprite {
                    custom_size: Some(Vec2::splat(block_size.width)),
                    color: food.food,
                    ..Default::default()
                },
                transform: Transform::from_translation(to_translation(
                    &window_size,
                    &block_size,
                    last_block.old_location.0,
                    last_block.old_location.1,
                )),
                ..Default::default()
            })
            .insert(Snake)
            .insert(Block {
                arrive: false,
                ..last_block
            });
    });
    next_move_state.set(MoveState::Ing);
}
fn check_collide(query: Query<&Block, With<Snake>>) {
    let query = query.iter().collect::<Vec<_>>();
    if query.len() != query.iter().collect::<HashSet<_>>().len() {
        panic!()
    }
}

fn eat(
    mut commands: Commands,
    food: Query<(Entity, &Block, &Handle<ColorMaterial>), With<Food>>,
    head: Query<&Block, With<Snake>>,
    mut next_food_state: ResMut<NextState<FoodState>>,
    mut food_eaten: EventWriter<FoodEaten>,
    mut materials: ResMut<Assets<ColorMaterial>>,
) {
    let food = food.get_single().unwrap();
    if food.1.location == head.iter().next().unwrap().location {
        commands.entity(food.0).despawn();
        next_food_state.set(FoodState::NonExist);
        food_eaten.send(FoodEaten {
            food: materials.get_mut(food.2).unwrap().color,
        });
    }
}
fn create_food(
    mut commands: Commands,
    mut meshs: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>,
    mut next_food_state: ResMut<NextState<FoodState>>,
    window_size: Res<WindowSize>,
    block_size: Res<BlockSize>,
    query: Query<&Block, With<Snake>>,
) {
    let (x, y) = {
        let body: HashSet<_> = query.into_iter().map(|e| e.location).collect();
        loop {
            let result = (
                thread_rng().gen_range(0..SIZE.0),
                thread_rng().gen_range(0..SIZE.1),
            );
            if body.get(&result).is_none() {
                break result;
            }
        }
    };
    let color = Color::rgb(random(), random(), random());
    commands
        .spawn(MaterialMesh2dBundle {
            mesh: meshs
                .add(shape::Circle::new(block_size.width / 2.).into())
                .into(),
            transform: Transform::from_translation(to_translation(&window_size, &block_size, x, y)),
            material: materials.add(ColorMaterial::from(color)),
            ..Default::default()
        })
        .insert(Block {
            location: (x, y),
            ..Default::default()
        })
        .insert(Food);
    next_food_state.set(FoodState::Exist);
}

fn to_translation(window_size: &WindowSize, block_size: &BlockSize, x: u32, y: u32) -> Vec3 {
    Vec3::new(
        (block_size.width * x as f32) - (window_size.width / 2.) + block_size.width / 2.,
        (block_size.width * y as f32) - (window_size.height / 2.) + block_size.width / 2.,
        0.,
    )
}
